﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Windows.Navigation;
using Com.PhilChuang.Pmvp.Ui.Views.Impl.ViewModels;
using Com.PhilChuang.Pmvp.Utils;
using Microsoft.Phone.Controls;

namespace Com.PhilChuang.Utils.Mobile
{
	public class NavigationHelper
	{
		//private static readonly Type s_PageType = typeof (PhoneApplicationPage);

		public event NavigatedEventHandler Navigated = delegate { };

		private readonly ManualResetEvent m_CurrentPageMre = new ManualResetEvent (false);
		private readonly Dictionary<Type, NavigationHelperMapping> m_PageTypeToMapping;
		private readonly ManualResetEvent m_SuspendingMre = new ManualResetEvent (true);
		private bool m_AppIsClosing = false;

		private NavigationService myNavigationService;
		public NavigationService NavigationService
		{
			get { return myNavigationService; }
			set
			{
				if (myNavigationService != null)
					UnregisterNavigationService ();
				myNavigationService = value;
				if (myNavigationService != null)
					RegisterNavigationService ();
			}
		}

		private PhoneApplicationPage myCurrentPage;
		public PhoneApplicationPage CurrentPage
		{
			get { return myCurrentPage; }
			set
			{
				if (ReferenceEquals (myCurrentPage, value)) return;
				myCurrentPage = value;
				NavigationService = myCurrentPage != null ? myCurrentPage.NavigationService : null;
				if (myCurrentPage != null)
					m_CurrentPageMre.Set ();
				else
					m_CurrentPageMre.Reset ();
			}
		}

		public PhoneApplicationPage WaitForCurrentPage()
		{
			m_CurrentPageMre.WaitOne ();
			return CurrentPage;
		}

		public NavigationHelper ()
		{
			m_PageTypeToMapping = new Dictionary<Type, NavigationHelperMapping> ();

			RegisterAppHelperEvents ();
		}

		public NavigationHelper (IEnumerable<NavigationHelperMapping> mappings)
		{
			mappings.ThrowIfNull ("mappings");

			m_PageTypeToMapping = mappings.ToDictionary (m => m.PageType);

			RegisterAppHelperEvents ();
		}

		private void RegisterAppHelperEvents ()
		{
			AppServiceLocator.WhenInstanceSet (
				() => AppServiceLocator.Instance.WhenImplCached<IAppHelper> (
					app => {
						app.Suspending += () => m_SuspendingMre.Reset ();
						app.Resuming += () => m_SuspendingMre.Set ();
						app.ResumingFromTombstone += () => m_SuspendingMre.Set ();
						app.Closing += () => m_AppIsClosing = true;
					}));
		}

		public void AddMapping (NavigationHelperMapping mapping)
		{
			m_PageTypeToMapping[mapping.PageType] = mapping;
		}

		private void RegisterNavigationService ()
		{
			if (myNavigationService == null) return;
			myNavigationService.Navigated += myNavigationService_Navigated;
		}

		private void myNavigationService_Navigated (object sender, NavigationEventArgs e)
		{
			CurrentPage = e.Content as PhoneApplicationPage;
			Navigated (sender, e);
		}

		private void UnregisterNavigationService ()
		{
			if (myNavigationService == null) return;
			myNavigationService.Navigated -= myNavigationService_Navigated;
		}

		public void NavigateTo<PageType> () where PageType : PhoneApplicationPage
		{
			NavigateTo<PageType> (null);
		}

		public void NavigateTo<PageType> (IList<KeyValuePair<String, String>> parms) where PageType : PhoneApplicationPage
		{
			if (CurrentPage.Dispatcher.InvokeIfRequired (() => NavigateTo<PageType> (parms)))
				return;

			m_SuspendingMre.WaitOne ();

			if (m_AppIsClosing) return;

			var type = typeof (PageType);
			if (!m_PageTypeToMapping.ContainsKey (type))
				throw new KeyNotFoundException ("No mapping found for {0}".FormatWith (type.Name));

			var url = new StringBuilder ();
			url.Append (m_PageTypeToMapping[type].PageUrl);
			if (parms != null && parms.Count > 0)
			{
				url.Append ('?');
				foreach (var kvp in parms)
					url.AppendFormat ("{0}={1}&", kvp.Key, kvp.Value);
				url.Remove (url.Length - 1, 1);
			}

			if (m_AppIsClosing) return;

			NavigationService.Navigate (new Uri (url.ToString (), UriKind.Relative));
		}

		/// <summary>
		/// Wires the given Page to its related ViewFacade and Presenter.
		/// </summary>
		/// <param name="page"></param>
		/// <returns>true if wiring occured</returns>
		public bool WireToPresenter (PhoneApplicationPage page)
		{
			page.ThrowIfNull ("page");
			
			var vm = page.DataContext as AbstractViewModel;
			if (vm != null && vm.IsAssociatedWithViewFacadeAndPage) return false; // not needed

			// we only get here when a page is loaded outside the control of a presenter
			var pageType = page.GetType ();
			NavigationHelperMapping mapping;
			if (!m_PageTypeToMapping.TryGetValue (pageType, out mapping))
				throw new Exception ("Could not wire up page {0}".FormatWith (pageType.Name));

			mapping.WireToViewFacadeAndPresenter (page);

			return true;
		}

		public String GetUrlFor<PageType> ()
			where PageType : PhoneApplicationPage
		{
			var pageType = typeof (PageType);
			NavigationHelperMapping mapping;
			if (!m_PageTypeToMapping.TryGetValue (pageType, out mapping))
				throw new Exception ("Could not find page {0}".FormatWith (pageType.Name));
			return mapping.PageUrl;
		}
	}

	public class NavigationHelperMapping
	{
		public Type PageType { get; set; }
		public String PageUrl { get; set; }
		public Action<PhoneApplicationPage> WireToViewFacadeAndPresenter { get; set; }

		public NavigationHelperMapping (Type pageType, String url, Action<PhoneApplicationPage> wireAction)
		{
			PageType = pageType;
			PageUrl = url;
			WireToViewFacadeAndPresenter = wireAction;
		}
	}
}
